// -----------------------------------------------------------------------
//  <copyright file="IdentityModuleBase.cs" company="LiuliuSoft">
//      Copyright (c) 2022-2025 DaprPlus. All rights reserved.
//  </copyright>
//  <site>https://dapr.plus</site>
//  <last-editor>郭明锋</last-editor>
//  <last-date>2025/1/14 23:33</last-date>
// -----------------------------------------------------------------------

using System.Security.Cryptography.X509Certificates;

using DaprPlus.AspNetCore;
using DaprPlus.Builder;
using DaprPlus.Exceptions;
using DaprPlus.Identity.Application;
using DaprPlus.Identity.Domain.Services;
using DaprPlus.Identity.Options;

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;

using Quartz;


namespace DaprPlus.Identity;

public abstract class IdentityModuleBase<TUser, TRole> : AspModuleBase
    where TUser : UserBase
    where TRole : RoleBase
{
    /// <summary>
    /// 获取 模块级别，级别越小越先启动
    /// </summary>
    public override ModuleLevel Level => ModuleLevel.Framework;

    /// <summary>
    /// 获取 模块启动顺序，模块启动的顺序按级别启动，同一级别内部再按此顺序启动
    /// 级别默认为0，表示无依赖，需要在同级别有依赖顺序的时候，再重写为>0的顺序值
    /// </summary>
    public override int Order => 2;

    /// <summary>
    /// 将模块服务添加到依赖注入服务容器中
    /// </summary>
    /// <param name="services">依赖注入服务容器</param>
    /// <returns></returns>
    public override IServiceCollection AddServices(IServiceCollection services)
    {
        // 添加基础服务
        AddBaseServices(services);

        // 配置Identity
        ConfigureIdentity(services);

        // 配置Quartz
        ConfigureQuartz(services);

        // 配置OpenIddict
        ConfigureOpenIddict(services);

        // 允许子类添加额外服务
        AddAdditionalServices(services);

        return services;
    }

    protected virtual void AddBaseServices(IServiceCollection services)
    {
        services.TryAddScoped<IValidateCodeService, ValidateCodeService>();
        services.Configure<IdentityServerOptions>(IdentityServerOptions.Position);
    }

    protected virtual void ConfigureIdentity(IServiceCollection services)
    {
        var identityBuilder = services.AddIdentityCore<TUser>(opts => ConfigureIdentityOptions(services, opts))
            .AddRoles<TRole>()
            .AddSignInManager();

        ConfigureIdentityBuilder(services, identityBuilder);

        services.Replace(new ServiceDescriptor(
            typeof(IdentityErrorDescriber),
            typeof(IdentityErrorDescriberZhHans),
            ServiceLifetime.Scoped));
    }

    protected virtual void ConfigureQuartz(IServiceCollection services)
    {
        services.AddQuartz(opts =>
        {
            ConfigureQuartzOptions(services, opts);
        });
        services.AddQuartzHostedService(opts => opts.WaitForJobsToComplete = true);
    }

    protected virtual void ConfigureQuartzOptions(IServiceCollection services, IServiceCollectionQuartzConfigurator opts)
    {
        opts.UseSimpleTypeLoader();
        opts.UseInMemoryStore();
    }

    protected virtual void ConfigureOpenIddict(IServiceCollection services)
    {
        var builder = services.AddOpenIddict();

        // 配置Core
        builder.AddCore(opts =>
        {
            ConfigureOpenIddictCore(services, opts);
            opts.UseQuartz();
        });

        // 配置Server
        builder.AddServer(opts =>
        {
            ConfigureOpenIddictServer(services, opts);
            ConfigureOpenIddictCertificate(services, opts);
        });

        // 允许额外的OpenIddict配置
        ConfigureAdditionalOpenIddict(services, builder);
    }

    protected virtual void ConfigureIdentityOptions(IServiceCollection services, IdentityOptions options)
    {
        var identityOptions = services.GetOptions<IdentityOptions>("Identity");
        options.Password = identityOptions.Password;
        options.User = identityOptions.User;
        options.ClaimsIdentity = identityOptions.ClaimsIdentity;
        options.Lockout = identityOptions.Lockout;
        options.SignIn = identityOptions.SignIn;
        options.Stores = identityOptions.Stores;
        options.Tokens = identityOptions.Tokens;
    }

    protected virtual void ConfigureIdentityBuilder(IServiceCollection services, IdentityBuilder builder)
    {
        builder.AddDefaultTokenProviders();
    }

    protected virtual void ConfigureOpenIddictCore(IServiceCollection services, OpenIddictCoreBuilder builder)
    {
        // 默认实现为空，允许子类重写
    }

    protected virtual void ConfigureOpenIddictServer(IServiceCollection services, OpenIddictServerBuilder builder)
    {
        var options = services.GetOptions<IdentityServerOptions>(IdentityServerOptions.Position);

        builder.SetTokenEndpointUris(options.TokenEndpoint)
            .SetAuthorizationEndpointUris(options.AuthorizeEndpoint)
            .SetEndSessionEndpointUris(options.EndSessionEndpoint)
            .SetUserInfoEndpointUris(options.UserInfoEndpoint);

        builder.RegisterScopes(options.AllowedScopes);

        builder.AllowAuthorizationCodeFlow()
            .AllowClientCredentialsFlow()
            .AllowRefreshTokenFlow();

        builder.DisableAccessTokenEncryption();

        if (options.UseReferenceTokens)
        {
            builder.UseReferenceAccessTokens()
                .SetIntrospectionEndpointUris(options.IntrospectionEndPoint);
        }
        builder.UseReferenceRefreshTokens();

        var aspBuilder = builder.UseAspNetCore();
        if (options.DisableTransportSecurity)
        {
            aspBuilder.DisableTransportSecurityRequirement();
        }

        aspBuilder.EnableTokenEndpointPassthrough()
            .EnableAuthorizationEndpointPassthrough()
            .EnableEndSessionEndpointPassthrough()
            .EnableUserInfoEndpointPassthrough()
            .EnableStatusCodePagesIntegration();
    }

    protected virtual void ConfigureOpenIddictCertificate(IServiceCollection services, OpenIddictServerBuilder builder)
    {
        var env = services.GetSingletonInstance<IWebHostEnvironment>();
        var options = services.GetOptions<IdentityServerOptions>(IdentityServerOptions.Position);

        if (env.IsDevelopment() || options.Certificates.UseDevelopmentCertificates)
        {
            ConfigureDevelopmentCertificates(builder);
            return;
        }

        ConfigureProductionCertificates(services, builder, options.Certificates);
    }

    protected virtual void ConfigureDevelopmentCertificates(OpenIddictServerBuilder builder)
    {
        builder.AddDevelopmentEncryptionCertificate()
               .AddDevelopmentSigningCertificate();
    }

    protected virtual void ConfigureProductionCertificates(IServiceCollection services, OpenIddictServerBuilder builder, CertificateConfiguration options)
    {
        if (options.EncryptionCertificate != null)
        {
            var certificate = LoadCertificate(
                options.EncryptionCertificate.Path,
                options.EncryptionCertificate.PassPhrasePath,
                ResIdentity.String_Init_EncryptionCertificatePassPhrase);

            builder.AddEncryptionCertificate(certificate);
        }

        if (options.SigningCertificate != null)
        {
            var certificate = LoadCertificate(
                options.SigningCertificate.Path,
                options.SigningCertificate.PassPhrasePath,
                ResIdentity.String_Init_SigningCertificatePassPhrase);

            builder.AddSigningCertificate(certificate);
        }
    }

    protected virtual X509Certificate2 LoadCertificate(string? path, string? passPhrasePath, string passPhraseErrorMessage)
    {
        if (string.IsNullOrEmpty(path))
        {
            throw new DaprPlusException(ResIdentity.String_Init_EncryptionCertificatePath);
        }

        var bytes = File.ReadAllBytes(path);
        var type = X509Certificate2.GetCertContentType(bytes);

        switch (type)
        {
            case X509ContentType.Cert:
                return X509CertificateLoader.LoadCertificate(bytes);

            case X509ContentType.Pfx:
                if (string.IsNullOrEmpty(passPhrasePath))
                {
                    throw new DaprPlusException(passPhraseErrorMessage);
                }
                return X509CertificateLoader.LoadPkcs12(bytes, File.ReadAllText(passPhrasePath));

            default:
                throw new DaprPlusException(ResIdentity.String_Init_EncryptionCertificatePath);
        }
    }

    protected virtual void ConfigureAdditionalOpenIddict(IServiceCollection services, OpenIddictBuilder builder)
    {
        // 默认实现为空，允许子类添加额外的OpenIddict配置
    }

    protected virtual void AddAdditionalServices(IServiceCollection services)
    {
        // 默认实现为空，允许子类添加额外服务
    }
}
